{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "5ce69936-9e12-4567-9326-8b40de683aea",
   "metadata": {},
   "source": [
    "这篇文章主要讲解如何使用GLM实现基本的微调，微调步骤如下：\n",
    "1. 训练阶段\n",
    "    1. 数据准备：将需要训练的任务以“问答”的方式组装成json格式\n",
    "    2. 切分分数：按比例将数据集切分训练集和验证集\n",
    "    3. 生成数据：分别将训练集和测试集以jsonl的形式保存成json文件\n",
    "    4. 开始训练：执行训练脚本，开始训练\n",
    "2. 推理阶段\n",
    "    1. 加载模型：使用transformers加载模型\n",
    "    2. 加载微调参数：加载训练后的微调参数，并替换到模型中\n",
    "    3. 推理：根据实际需求进行模型推理"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8b156804-1d2f-474f-ab00-dfbd7fda2561",
   "metadata": {},
   "source": [
    "案例中使用经典的中文新闻标题分类为例子：https://aistudio.baidu.com/aistudio/competition/detail/809/0/introduction"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2f38315d-72d5-43a2-8ea2-cea7684b73ff",
   "metadata": {},
   "source": [
    "# 训练阶段"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "85190b23-d2cc-4e3d-bb49-4d347d605db7",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>title</th>\n",
       "      <th>label</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>网民市民集体幻想中奖后如果你中了9000万怎么办</td>\n",
       "      <td>彩票</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>PVC期货有望5月挂牌</td>\n",
       "      <td>财经</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>午时三刻新作《幻神录―宿命情缘》</td>\n",
       "      <td>游戏</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>欧司朗LLFY网络提供一站式照明解决方案</td>\n",
       "      <td>家居</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>试探北京楼市向何方：排不完的队　涨不够的价</td>\n",
       "      <td>房产</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>...</th>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>79995</th>\n",
       "      <td>王大雷看国足比赛预测比分我觉得是2-0或者3-1</td>\n",
       "      <td>体育</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>79996</th>\n",
       "      <td>克雷扎回归猛龙势如破竹希尔遭驱逐太阳惨败51分</td>\n",
       "      <td>体育</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>79997</th>\n",
       "      <td>王建宙将与台商共创4G网络商机</td>\n",
       "      <td>科技</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>79998</th>\n",
       "      <td>普京突访食品超市做调查不满高价猪肉(图)</td>\n",
       "      <td>时政</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>79999</th>\n",
       "      <td>高空俯视女明星性感乳沟(组图)(7)</td>\n",
       "      <td>时尚</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "<p>80000 rows × 2 columns</p>\n",
       "</div>"
      ],
      "text/plain": [
       "                          title label\n",
       "0      网民市民集体幻想中奖后如果你中了9000万怎么办    彩票\n",
       "1                   PVC期货有望5月挂牌    财经\n",
       "2              午时三刻新作《幻神录―宿命情缘》    游戏\n",
       "3          欧司朗LLFY网络提供一站式照明解决方案    家居\n",
       "4         试探北京楼市向何方：排不完的队　涨不够的价    房产\n",
       "...                         ...   ...\n",
       "79995  王大雷看国足比赛预测比分我觉得是2-0或者3-1    体育\n",
       "79996   克雷扎回归猛龙势如破竹希尔遭驱逐太阳惨败51分    体育\n",
       "79997           王建宙将与台商共创4G网络商机    科技\n",
       "79998      普京突访食品超市做调查不满高价猪肉(图)    时政\n",
       "79999        高空俯视女明星性感乳沟(组图)(7)    时尚\n",
       "\n",
       "[80000 rows x 2 columns]"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import pandas as pd\n",
    "from tqdm import tqdm\n",
    "import random\n",
    "# 读取数据\n",
    "train_df = pd.read_csv(\"D:/360安全浏览器下载/train.txt\", delimiter=\"\\t\", header=None, names=['title', 'label'])\n",
    "dev_df = pd.read_csv(\"D:/360安全浏览器下载/dev.txt\", delimiter=\"\\t\", header=None, names=['title', 'label'])\n",
    "dev_df"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "8828a573-7431-4064-8a28-ecc5084a1d1d",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|███████████████████████████████████████████████████████████████████████| 752471/752471 [00:25<00:00, 29294.04it/s]\n",
      "100%|█████████████████████████████████████████████████████████████████████████| 80000/80000 [00:02<00:00, 29440.16it/s]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "最大问题长度:119, 最大答案长度:2\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "{'content': '给定文本内容为“网易第三季度业绩低于分析师预期”，文本的分类包含：财经、彩票、房产、股票、家居、教育、科技、社会、时尚、时政、体育、星座、游戏、娱乐。请问文本属于什么类型？',\n",
       " 'response': '科技'}"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 将数据转换成GPT可以使用的护具\n",
    "def change_data(data_df, max_content_len, max_response_len):\n",
    "    # 定义prompt\n",
    "    prompt = '给定文本内容为“{}”，文本的分类包含：财经、彩票、房产、股票、家居、教育、科技、社会、时尚、时政、体育、星座、游戏、娱乐。请问文本属于什么类型？'\n",
    "    # 初始化GPT的数据\n",
    "    chat_data = []\n",
    "    # 遍历数据集，生成GLM需要的数据\n",
    "    for idx, row in tqdm(data_df.iterrows(), total=len(data_df)):\n",
    "        content = prompt.format(row['title'])\n",
    "        chat_data.append({\n",
    "            'content': content, # 问题, key可以自行替换\n",
    "            'response': row['label']                # 答案, key可以自行替换\n",
    "        })\n",
    "        if len(content) > max_content_len:\n",
    "            max_content_len = len(content)\n",
    "        if len(row['label']) > max_response_len:\n",
    "            max_response_len = len(row['label'])\n",
    "    return chat_data, max_content_len, max_response_len\n",
    "\n",
    "# 记录最大长度\n",
    "max_content_len, max_response_len = 0, 0\n",
    "# 转换数据\n",
    "chat_train_data, max_content_len, max_response_len = change_data(train_df, max_content_len, max_response_len)\n",
    "chat_dev_data, max_content_len, max_response_len = change_data(dev_df, max_content_len, max_response_len)\n",
    "\n",
    "print('最大问题长度:{}, 最大答案长度:{}'.format(max_content_len, max_response_len))\n",
    "chat_train_data[0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0bdaaba1-c2c5-47b3-a514-d273f209cb1e",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 保存数据\n",
    "random.shuffle(chat_train_data)\n",
    "random.shuffle(chat_dev_data)\n",
    "\n",
    "with open('train.json', 'w') as up:\n",
    "    for line in chat_train_data:\n",
    "        up.write(json.dumps(line)+'\\n')\n",
    "    \n",
    "with open('dev.json', 'w') as up:\n",
    "    for line in chat_dev_data:\n",
    "        up.write(json.dumps(line)+'\\n')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ad11c366-5f5e-41ba-baea-1959e28188d9",
   "metadata": {},
   "source": [
    "有了数据以后，就可以开始训练模型。具体训练文档见：https://github.com/THUDM/ChatGLM-6B/tree/main/ptuning\n",
    "\n",
    "其中需要注意的参数未：\n",
    "- PRE_SEQ_LEN：输出句子长度\n",
    "- LR：训练的学习率\n",
    "- /home/jxbd/GLM/GLM6B/ChatGLM/ptuning/main.py：clone代码的绝对路径\n",
    "- train_file：生成的训练数据\n",
    "- validation_file：生成的校验数据\n",
    "- prompt_column：数据中问题的键\n",
    "- response_column：数据中答案的键\n",
    "- model_name_or_path：本地模型的绝对路径\n",
    "- output_dir：微量参数保存的绝对路径\n",
    "- max_source_length：问题最大句子长度\n",
    "- max_target_length：答案最大句子长度\n",
    "- per_device_train_batch_size：训练集批大小\n",
    "- per_device_eval_batch_size：验证集批大小\n",
    "- gradient_accumulation_steps：梯度更新的步骤\n",
    "- max_steps：训练次数\n",
    "- logging_steps：日志打印步骤\n",
    "- save_steps：中间模型保存次数\n",
    "\n",
    "其中per_device_train_batch_size、per_device_eval_batch_size需要根据自身显存进行调整。通常情况下，缩小句子长度和批大小大幅降低显存的需求。而gradient_accumulation_steps则视训练集的批大小进行调整。</br>\n",
    "假设我需要每**16笔数据**更新一次模型，我们就需要令 $$梯度更新的步骤*训练集批大小=16$$"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9187f522-5609-47c2-b36f-a331afe73fec",
   "metadata": {},
   "source": [
    "基于上述，生成具体的train.sh文件。内容如下：\n",
    "```bash\n",
    "PRE_SEQ_LEN=2\n",
    "LR=1e-3\n",
    "\n",
    "CUDA_VISIBLE_DEVICES=0 python3 /home/jxbd/GLM/GLM6B/ChatGLM/ptuning/main.py \\\n",
    "    --do_train \\\n",
    "    --train_file /home/jxbd/python/glm/train.json \\\n",
    "    --validation_file /home/jxbd/python/glm/dev.json \\\n",
    "    --prompt_column context \\\n",
    "    --response_column response \\\n",
    "    --overwrite_cache \\\n",
    "    --model_name_or_path /home/jxbd/python/bertmodel/bertmodel/THUDM/chatglm-6b \\\n",
    "    --output_dir /home/jxbd/python/glm/model \\\n",
    "    --overwrite_output_dir \\\n",
    "    --max_source_length 119 \\\n",
    "    --max_target_length 2 \\\n",
    "    --per_device_train_batch_size 16 \\\n",
    "    --per_device_eval_batch_size 1 \\\n",
    "    --gradient_accumulation_steps 1 \\\n",
    "    --predict_with_generate \\\n",
    "    --max_steps 1000 \\\n",
    "    --logging_steps 10 \\\n",
    "    --save_steps 500 \\\n",
    "    --learning_rate $LR \\\n",
    "    --pre_seq_len $PRE_SEQ_LEN \n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f5961fd8-cce2-4ad0-a356-cbe74d29dbf7",
   "metadata": {},
   "source": [
    "执行脚本"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ec0d1a04-c805-49d9-976b-0c1757816773",
   "metadata": {},
   "outputs": [],
   "source": [
    "!train.sh"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1ec124b2-5b78-4d23-a725-171a9635f219",
   "metadata": {},
   "source": [
    "# 预测阶段"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4cc6e445-90d9-4855-a5c1-20064b0c6aad",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 首先载入Tokenizer\n",
    "import torch\n",
    "from transformers import AutoTokenizer, AutoModel, AutoConfig\n",
    "\n",
    "# 加载 Checkpoint\n",
    "config = AutoConfig.from_pretrained(\"/home/jxbd/python/数据创新/数据治理规则/glm/checkpoint-3000\", trust_remote_code=True)\n",
    "tokenizer = AutoTokenizer.from_pretrained(\"/home/jxbd/python/数据创新/数据治理规则/glm/checkpoint-3000\", trust_remote_code=True)\n",
    "# 原始\n",
    "model = AutoModel.from_pretrained(\"/home/jxbd/python/bertmodel/bertmodel/THUDM/chatglm-6b\", trust_remote_code=True, config=config).half()\n",
    "\n",
    "# 本次微调得到的glm权重\n",
    "prefix_state_dict = torch.load('/home/jxbd/python/数据创新/数据治理规则/glm/checkpoint-3000/pytorch_model.bin')\n",
    "new_prefix_state_dict = {}\n",
    "for k, v in prefix_state_dict.items():\n",
    "    if k.startswith(\"transformer.prefix_encoder.\"):\n",
    "        new_prefix_state_dict[k[len(\"transformer.prefix_encoder.\"):]] = v\n",
    "model.transformer.prefix_encoder.load_state_dict(new_prefix_state_dict)\n",
    "\n",
    "# 据需求可以进行量化\n",
    "# model = model.quantize(4)\n",
    "# 模型写入GPU\n",
    "model = model.half().cuda()\n",
    "model.transformer.prefix_encoder.float()\n",
    "model = model.eval()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c889c74a-cb0d-4cd2-8832-a19b64f14d70",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 生成问题\n",
    "request = '给定文本内容为“网易第三季度业绩低于分析师预期”，文本的分类包含：财经、彩票、房产、股票、家居、教育、科技、社会、时尚、时政、体育、星座、游戏、娱乐。请问文本属于什么类型？'\n",
    "# 单次提问\n",
    "response, history = model.chat(tokenizer, request, history=[])\n",
    "response"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
